/*
 * ModelCC, distributed under ModelCC Shared Software License, www.modelcc.org
 */

package org.modelcc.language.syntax;

import java.io.Serializable;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.modelcc.AssociativityType;
import org.modelcc.language.CyclicPrecedenceException;
import org.modelcc.language.metamodel.LanguageModel;

/**
 * Constraint factory.
 * 
 * @author Luis Quesada (lquesada@modelcc.org), refactored by Fernando Berzal (fberzal@modelcc.org)
 */
public final class SyntaxConstraintsFactory implements Serializable 
{
	private SyntaxConstraints constraints;
	
    /**
     * Default constructor.
     */
    public SyntaxConstraintsFactory() 
    {
    	constraints = new SyntaxConstraints();
    }

    /**
     * Set an associativity constraint for an object type.
     * @param type the object type.
     * @param as the associativity constraint.
     */
    public void setAssociativity(Object type, AssociativityType as) 
    {
    	constraints.setAssociativity(type, as);
    }

    /**
     * Adds a composition precedence between rules.
     * @param ts1 the rule that precedes.
     * @param ts2 the rule that is preceded.
     */
    public void addCompositionPrecedences(Rule ts1,Rule ts2) 
    {
    	constraints.addCompositionPrecedences(ts1, ts2);
    }

    /**
     * Adds a start precedence between rules.
     * @param ts1 the rule that precedes.
     * @param ts2 the rule that is preceded.
     */
    public void addStartPrecedences(Rule ts1,Rule ts2) 
    {
    	constraints.addStartPrecedences(ts1, ts2);
    }

    /**
     * Removes a composition precedence between rules.
     * @param ts1 the rule that precedes.
     * @param ts2 the rule that is preceded.
     */
    public void removeCompositionPrecedences(Rule ts1,Rule ts2) 
    {
    	constraints.removeCompositionPrecedences(ts1, ts2);
    }

    /**
     * Adds an selection precedence between rules.
     * @param ts1 the rule that precedes.
     * @param ts2 the rule that is preceded.
     */
    public void addSelectionPrecedences(Rule ts1,Rule ts2) 
    {
    	constraints.addSelectionPrecedences(ts1, ts2);
    }

    /**
     * Removes an selection precedence between rules.
     * @param ts1 the rule that precedes.
     * @param ts2 the rule that is preceded.
     */
    public void removeSelectionPrecedences(Rule ts1,Rule ts2) 
    {
    	constraints.removeSelectionPrecedences(ts1, ts2);
    }

    /**
     * Generates a syntactic specification.
     * @throws CyclicCompositionPrecedenceException whenever several rules mutually precede with a composition precedence.
     * @throws CyclicSelectionPrecedenceException whenever several rules mutually precede with an selection precedence.
     * @return the syntactic specification.
     */
    public SyntaxConstraints create() 
    	throws CyclicPrecedenceException 
    {
    	return create(null);
    }
    
    public SyntaxConstraints create(LanguageModel model) 
    	throws CyclicPrecedenceException 
    {
        checkCompositionPrecedences();
        checkSelectionPrecedences();

        constraints.setModel(model);
        
        return constraints;
    }

    
    // Checkers
    
    public void checkSelectionPrecedences ()
    	throws CyclicPrecedenceException
    {
    	Set<Rule> rules = cycle(constraints.getSelectionPrecedences());
    	
    	if (!rules.isEmpty())
			throw new CyclicPrecedenceException("Cyclic selection precedence exception: "+cycleMessage(rules));
    }

    public void checkCompositionPrecedences()
			throws CyclicPrecedenceException 
	{
    	Set<Rule> rules = cycle(constraints.getCompositionPrecedences());
    	
    	if (!rules.isEmpty())
			throw new CyclicPrecedenceException("Cyclic composition precedence exception: "+cycleMessage(rules));
	}
    
    
    public String cycleMessage (Set<Rule> rules)
    {
    	String list = "";

    	for (Rule r: rules)
    		list += " "+r.getLeft().getType();

    	return list;
    }
    
    // Check cyclic precedences

    public Set<Rule> cycle (Map<Rule, Set<Rule>> precedences)
	{
    	Set<Rule> pool = new HashSet<Rule>();
    	Set<Rule> preceded; // Rules preceded by any rule in the pool.
    	boolean found;

    	for (Entry<Rule, Set<Rule>> e: precedences.entrySet()) {
    		pool.add(e.getKey());
    		for (Rule r: e.getValue()) {
    			pool.add(r);
    		}
    	}

    	while (!pool.isEmpty()) {

    		found = false;

    		preceded = new HashSet<Rule>();
    		for (Rule r: pool) {
    			if (precedences.get(r) != null)
    				preceded.addAll(precedences.get(r));
    		}

    		Iterator<Rule> ite = pool.iterator();
    		
    		while (ite.hasNext()) {
    			Rule r = ite.next();
    			if (!preceded.contains(r)) {
    				ite.remove();
    				found = true;
    			}
    		}

    		if (!found)
    			return pool;
    	}
    	
    	return pool;
	}    
}
